﻿using System;
using System.Collections; 
using System.Collections.Generic;
using System.IO; 
using System.Text;

namespace Parser
{
    public delegate bool CharTestDelegate(char c);
    
    public interface ICharTest
    {
        bool Test(char c);
    }

    public class AnyChar : ICharTest
    {
        public bool Test(char c)
        {
            return true; 
        }

        public override string ToString()
        {
            return "<AnyChar>";
        }
    }

    public class CharOneOf : ICharTest
    {
        private char[] oneOf = { };
        public CharOneOf(char[] oneOf)
        {
            this.oneOf = oneOf; 
        }

        public bool Test(char c)
        {
            return Array.Exists(this.oneOf, delegate(char ch)
            {
                return ch == c;
            });
        }

        public override string ToString()
        {
            return string.Format("<CharOneOf:{0}>", StringUtil.ArrayToString(this.oneOf));
        }
    }

    public class CharExceptIn : ICharTest
    {
        private char[] oneOf = { };
        public CharExceptIn(char[] oneOf)
        {
            this.oneOf = oneOf; 
        }

        public bool Test(char c)
        {
            return !Array.Exists(this.oneOf, delegate(char ch)
            {
                return ch == c;
            });
        }

        public override string ToString()
        {
            return string.Format("<CharExceptIn:{0}>", StringUtil.ArrayToString(this.oneOf));
        }
    }

    public class CharInRange : ICharTest
    {
        private char low;
        private char high;
        public CharInRange(char low, char high)
        {
            this.low = low;
            this.high = high;
        }

        public bool Test(char c)
        {
            return low <= c && c <= high; 
        }

        public override string ToString()
        {
            return string.Format("<CharInRange:{0}, {1}>", this.low, this.high);
        }
    }

    public class CharMatchOneOf : ICharTest
    {
        private ICharTest[] members;
        public CharMatchOneOf(ICharTest[] members)
        {
            this.members = members; 
        }
        public bool Test(char c)
        {
            foreach (ICharTest test in this.members)
            {
                if (test.Test(c))
                    return true;
            }
            return false;
        }
        public override string ToString()
        {
            return string.Format("<CharMatchOneOf:{0}>", StringUtil.ArrayToString(this.members));
        }
    }

    public class CharMatchAllOf : ICharTest
    {
        private ICharTest[] members;
        public CharMatchAllOf(ICharTest[] members)
        {
            this.members = members;
        }
        public bool Test(char c)
        {
            foreach (ICharTest test in this.members)
            {
                if (!test.Test(c))
                    return false;
            }
            return true;
        }
        public override string ToString()
        {
            return string.Format("<CharMatchAllOf:{0}>", StringUtil.ArrayToString(this.members));
        }
    }

    public class CharParser2 : BaseParser , ICharTest
    {
        /* what do we do with char delegate? we need a way to report errors - it means that we need to track better information on the delegate itself.
         * we need to have a way to 
         */
        private ICharTest test;
        private bool negate = false; // this negates the test. 
        public CharParser2(ICharTest test)
        {
            this.test = test;
        }

        public override Result InnerParse(BinaryReader reader, long pos)
        {
            if (pos != reader.BaseStream.Position)
            {
                reader.BaseStream.Position = pos;
            }
            int ch;
            if ((ch = reader.PeekChar()) != -1)
            {
                if (this.test.Test(Convert.ToChar(ch)))
                {
                    char c = reader.ReadChar();
                    return new Result(c, reader.BaseStream.Position); // position is updated. 
                }
                else
                {
                    return Result.MakeFail(reader, pos);
                }
            }
            else
            {
                return Result.MakeFail(reader, pos);
            }
        }

        public bool Test(char c)
        {
            bool result = this.test.Test(c);
            if (negate)
            {
                return !result;
            }
            else
            {
                return result; 
            }
        }

        public override string ToString()
        {
            return string.Format("<CharParser:{0}>", this.test); 
        }

        public static CharParser2 Any()
        {
            return new CharParser2(new AnyChar());
        }

        public static CharParser2 OneOf(params char[] members)
        {
            return new CharParser2(new CharOneOf(members));
        }

        public static CharParser2 Equals(char c)
        {
            return OneOf(c);
        }

        public static CharParser2 ExceptIn(params char[] members)
        {
            return new CharParser2(new CharExceptIn(members));
        }

        public static CharParser2 NotEquals(char c)
        {
            return ExceptIn(c); 
        }

        public static CharParser2 InRange(char low, char high)
        {
            return new CharParser2(new CharInRange(low, high));
        }

        public static CharParser2 IsDigit()
        {
            return InRange('0', '9');
        }

        public static CharParser2 IsLowerAlpha()
        {
            return InRange('a', 'z');
        }

        public static CharParser2 IsUpperAlpha()
        {
            return InRange('A', 'Z');
        }

        public static CharParser2 IsAlpha()
        {
            return new CharParser2(new CharMatchOneOf(new CharInRange[2]{ new CharInRange('a','z'), new CharInRange('A','Z') }));
        }

        public static CharParser2 IsAlphaNumeric()
        {
            return new CharParser2(new CharMatchOneOf(new CharInRange[3] { new CharInRange('a', 'z'), new CharInRange('A', 'Z'), new CharInRange('0','9') }));
        }

    }
}
